package com.ruyuan.user.server.domain.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.ruyuan.common.model.BusinessException;
import com.ruyuan.common.model.ErrorCode;
import com.ruyuan.common.model.PageDTO;
import com.ruyuan.user.server.domain.converter.OrganizationConverter;
import com.ruyuan.user.server.domain.model.Organization;
import com.ruyuan.user.server.domain.model.OrganizationCacheWrapper;
import com.ruyuan.user.server.domain.service.OrganizationDomainService;
import com.ruyuan.user.server.domain.service.request.PageQueryOrganizationParam;
import com.ruyuan.user.server.repository.bean.OrganizationDO;
import com.ruyuan.user.server.repository.bean.OrganizationExample;
import com.ruyuan.user.server.repository.mapper.OrganizationMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xx
 */
@Service
public class OrganizationDomainServiceImpl implements OrganizationDomainService {

    @Autowired
    private OrganizationMapper organizationMapper;

    @Autowired
    private OrganizationConverter converter;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final String ORG_CACHE_PREFIX = "orgPrefix:";

    private static final Long LOCAL_REFRESH_TIME = 30L;

    private static final Long REDIS_REFRESH_TIME = 7L;

    private static final Long WRAPPER_INVALID_TIME = 5 * 60 * 1000L;

    private ExecutorService threadPool = new ThreadPoolExecutor(5, 10, 30L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(Integer.MAX_VALUE));

    private LoadingCache<Long, Optional<Organization>> orgCache = CacheBuilder.newBuilder()
            .expireAfterWrite(LOCAL_REFRESH_TIME, TimeUnit.SECONDS).build(
                    new CacheLoader<Long, Optional<Organization>>() {
                        @Override
                        public Optional<Organization> load(Long orgId) throws Exception {
                            BoundValueOperations<String, OrganizationCacheWrapper> valueOps = redisTemplate.boundValueOps(ORG_CACHE_PREFIX + orgId);
                            OrganizationCacheWrapper organizationCacheWrapper = valueOps.get();
                            if (organizationCacheWrapper == null) {
                                OrganizationDO organizationDO = organizationMapper.selectByPrimaryKey(orgId);
                                organizationCacheWrapper = new OrganizationCacheWrapper();
                                organizationCacheWrapper.setInvalidTime(System.currentTimeMillis() + WRAPPER_INVALID_TIME);
                                organizationCacheWrapper.setOrganizationDO(organizationDO);
                                valueOps.set(organizationCacheWrapper, REDIS_REFRESH_TIME, TimeUnit.MINUTES);
                            } else if (organizationCacheWrapper.getInvalidTime() < System.currentTimeMillis()) {
                                threadPool.submit(() -> {
                                    String lockKey = ORG_CACHE_PREFIX + orgId + ":lock";
                                    Boolean lockResult = redisTemplate.boundValueOps(lockKey).setIfAbsent("1", 2L, TimeUnit.SECONDS);
                                    if (lockResult != null && lockResult) {
                                        //重新检查一遍是否已经被刷新成功
                                        OrganizationCacheWrapper cacheAfterLock = valueOps.get();
                                        //已经被处理过就不处理了
                                        if (cacheAfterLock != null && cacheAfterLock.getInvalidTime() > System.currentTimeMillis()) {
                                            return;
                                        }
                                        OrganizationDO organizationDO = organizationMapper.selectByPrimaryKey(orgId);
                                        OrganizationCacheWrapper newWrapper = new OrganizationCacheWrapper();
                                        newWrapper.setInvalidTime(System.currentTimeMillis() + WRAPPER_INVALID_TIME);
                                        newWrapper.setOrganizationDO(organizationDO);
                                        valueOps.set(newWrapper, REDIS_REFRESH_TIME, TimeUnit.MINUTES);
                                        // 释放锁
                                        redisTemplate.delete(lockKey);
                                    }
                                });
                            }
                            return Optional.ofNullable(converter.convertDOToModel(organizationCacheWrapper.getOrganizationDO()));
                        }
                    }
            );

    @Override
    public Long saveOrganization(Organization organization) {
        if (organization.getParentId() == null) {
            //todo 优化异常
            throw new BusinessException(ErrorCode.code("EMPTY_PARAM", "parentId"));
        }
        // 清空组织架构，根据业务逻辑来重新生成
        organization.setOrgPath(null);
        organization.setRootId(null);
        if (organization.getParentId() > 0L) {
            Organization parentOrg = queryOrganizationById(organization.getParentId());
            if (parentOrg == null) {
                throw new BusinessException(ErrorCode.code("PARENT_ORG_NOT_EXIST"));

            }
            organization.setRootId(parentOrg.getRootId());
            ArrayList<Long> parentOrgPath = new ArrayList<>(parentOrg.getOrgPath());
            parentOrgPath.add(parentOrg.getId());
            organization.setOrgPath(parentOrgPath);
        }
        OrganizationDO organizationDO = converter.convertModelToDO(organization);
        if (organizationDO.getId() != null) {
            if (organizationDO.getParentId() == 0L) {
                // parentId为0，说明想更新成根节点
                organizationDO.setRootId(organizationDO.getId());
                organizationDO.setOrgPath(null);
            }
            //updateByPrimaryKey 如果某个字段为null也会被设置进去，
            //而updateByPrimaryKeySelective则不会
            organizationMapper.updateByPrimaryKey(organizationDO);
        } else {
            organizationMapper.insert(organizationDO);
            if (organizationDO.getParentId() == 0L) {
                organizationDO.setRootId(organizationDO.getId());
                organizationMapper.updateByPrimaryKey(organizationDO);
            }
        }
        redisTemplate.delete(ORG_CACHE_PREFIX + organizationDO.getId());
        return organizationDO.getId();
    }

    @Override
    public Organization queryOrganizationById(Long orgId) {
        try {
            return orgCache.get(orgId).orElse(null);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.code("ORG_QUERY_ERROR"));
        }
    }

    @Override
    public PageDTO<Organization> queryOrganizationByCondition(PageQueryOrganizationParam param) {
        OrganizationExample organizationExample = new OrganizationExample();
        OrganizationExample.Criteria criteria = organizationExample.createCriteria();
        criteria.andRootIdEqualTo(param.getRootOrgId());
        criteria.andIsDeletedEqualTo("n");
        if (param.getParentId() != null) {
            criteria.andParentIdEqualTo(param.getParentId());
        }
        if (StringUtils.isNotBlank(param.getOrgName())) {
            criteria.andNameLike("%" + param.getOrgName() + "%");
        }
        PageHelper.startPage(param.getCurrent(), param.getPageSize());
        Page<OrganizationDO> page = (Page<OrganizationDO>) organizationMapper.selectByExample(organizationExample);
        PageDTO<Organization> orgPageDTO = new PageDTO<>();
        List<OrganizationDO> result = page.getResult();
        if (!CollectionUtils.isEmpty(result)) {
            orgPageDTO.setData(result.stream().map(converter::convertDOToModel).collect(Collectors.toList()));
        }
        orgPageDTO.setPages(page.getPages());
        orgPageDTO.setPageSize(page.getPageSize());
        orgPageDTO.setCurrent(param.getCurrent());
        orgPageDTO.setTotal(page.getTotal());
        orgPageDTO.setCurPageSize(result.size());
        return orgPageDTO;
    }
}
